﻿#region using statements

using System.Collections.Generic;

#endregion

namespace NGen.Systems.FSM
{
	/// <summary>
	/// 	Stack based finite state machine. Top of stack is current state.
	/// </summary>
	/// <typeparam name = "TStateType">the type of states being managed</typeparam>
	/// <typeparam name = "TInterfaceType">the type of Fsm (class inheriting the Fsm)</typeparam>
	public class Fsm<TStateType, TInterfaceType>
		where TStateType : FsmState<TStateType, TInterfaceType>
		where TInterfaceType : Fsm<TStateType, TInterfaceType>
	{
		#region local fields

		/// <summary>
		/// 	the state stack
		/// </summary>
		private readonly Stack<TStateType> m_stateStack = new Stack<TStateType>();

		#endregion

		#region public methods

		/// <summary>
		/// 	push a new state on the stack and call OnEnter method
		/// </summary>
		/// <param name = "p_newState">the state to push</param>
		public void Push(TStateType p_newState) {
			NotifyOnExit();

			m_stateStack.Push(p_newState);
			p_newState.m_stateMachine = this;

			NotifyOnEnter();
		}

		/// <summary>
		/// 	pop the top state from the stop and call on Exit. Repeated for the entire stack if true is passed.
		/// </summary>
		/// <param name = "p_clearStack">should the stack be cleared</param>
		/// <returns>was anything successfully popped</returns>
		public bool Pop(bool p_clearStack = false) {
			if(m_stateStack.Count == 0) {
				return false;
			}

			if(p_clearStack) {
				while(m_stateStack.Count > 0) {
					m_stateStack.Pop().OnExit();
				}
			} else {
				m_stateStack.Pop().OnExit();
			}

			NotifyOnEnter();

			return true;
		}

		/// <summary>
		/// 	pop the top state from the statck, then push a new one
		/// </summary>
		/// <param name = "p_newState">the state to push</param>
		/// <param name = "p_clearStack">should the stack be cleared</param>
		public void Change(TStateType p_newState, bool p_clearStack = false) {
			if(p_clearStack) {
				while(m_stateStack.Count > 0) {
					m_stateStack.Pop().OnExit();
				}
			} else {
				m_stateStack.Pop().OnExit();
			}

			m_stateStack.Push(p_newState);
			p_newState.m_stateMachine = this;

			NotifyOnEnter();
		}

		#endregion

		#region protected methods

		/// <summary>
		/// 	notify the top state on the stack that it has become the new active state
		/// </summary>
		protected void NotifyOnEnter() {
			if(m_stateStack.Count > 0) {
				m_stateStack.Peek().OnEnter();
			}
		}

		/// <summary>
		/// 	notify the top state on the stack that it is no longer the active state
		/// </summary>
		protected void NotifyOnExit() {
			if(m_stateStack.Count > 0) {
				m_stateStack.Peek().OnExit();
			}
		}

		#endregion
	}
}